/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c) 2002, GeoVISTA Center
 * Licensed under Simplified BSD License
 * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
 *
 * @original author: jin Chen 
 * @author: jin Chen 
 * @date: Feb 23, 2007$
 * @version: 1.0
 */
package edu.psu.geovista.app.scatterplot;


import edu.psu.geovista.app.visualclassifier.classify.v2.ESVisualClassifier;
import edu.psu.geovista.app.visualclassifier.classify.v2.VisualClassifierClient;
import edu.psu.geovista.app.visualclassifier.classify.v2.agent.ClassificationInterestor;
import edu.psu.geovista.app.visualclassifier.classify.v2.agent.ClassifyAgent;
import edu.psu.geovista.app.visualclassifier.classify.v2.gui.LegendControl;
import edu.psu.geovista.app.visualclassifier.classify.v2.gui.LegendControl2;
import edu.psu.geovista.classification.ClassificationUtils;
import edu.psu.geovista.classification.GroupClassificationResult;
import edu.psu.geovista.classification.setting.ClassifySetting;
import edu.psu.geovista.common.awt.event.dataloading.AppDataEvent;
import edu.psu.geovista.common.debug.Debug;
import edu.psu.geovista.data.geog.DataSetForApps;
import edu.psu.geovista.data.model.AppDataModel;
import edu.psu.geovista.symbolization.BivariateColorSymbolClassification;
import edu.psu.geovista.symbolization.event.ColorClassifierEvent;
import edu.psu.geovista.symbolization.event.ColorClassifierListener;
import edu.psu.geovista.ui.event.*;
import epoviz.trex.common.swing.frame.AppComponent;
import epoviz.trex.common.swing.utils.ImageUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Vector;

public class SimpleScatterPlot extends JPanel
        implements  AppComponent, VisualClassifierClient,
        DataSetListener, ActionListener,
        ColorClassifierListener, SelectionListener,IndicationListener,ColorArrayListener, VariableSelectionListener,
        ClassificationResultListener,ClassificationInterestor,GroupClassifyListener{
    transient private ScatterPlot scatterPlot;
    transient private int[] displayIndices = new int[2];  //index in data model of 2 variables that displayed in scatter plot

    transient private JToolBar toolbar;
    //transient private BivariateColorSchemeVisualizer biViz;
    transient private Color backgroundColor;
    //transient private Vector selectedObvs;
    transient private int[] selections;// 
    //transient private int[] selectedObvsInt;
    transient private Color[] multipleSelectionColors;
    private JComboBox xcb; //set what variable to display on X Axis
    private JComboBox ycb; //set what variable to display on Y Axis
    transient private Color[] colorArrays;
    transient int rowcount;//data row count
    //protected LegendControl2 legend;

    boolean external =false;

    //HashMap nameIndexs=new HashMap();//maintain index of variables in original dataSet. For now scatterplot only accept index to display 2 particular variables
    //AttributesNameIndexs attIndex;//keep index of attributes of data
    ClassifyAgent classifyAgent;// 
    boolean showLegend=true;

    public SimpleScatterPlot() {

        this.setLayout(new BorderLayout());
        //this.add(topContent, BorderLayout.NORTH);
        scatterPlot = new MyScatterPlot(this);
        LinearRegression lg=new LinearRegression();
        this.setRegressionClass(lg);

        scatterPlot.setLayout(null);
        // 
        this.classifyAgent=new ClassifyAgent(this);
       // JComponent legend=(JComponent) classifyAgent.getLegendInterative();
        //legend = new LegendControl2(LegendControl1.getSampleColors());
        //epoviz.trex.common.swing.layout.auto.ContaineeDragger.enableDragAnywhere(scatterPlot,legend,epoviz.trex.common.swing.layout.auto.ContaineeLocator.TOP_RIGHT,ContaineeLocator.CUSTOMIZE_REL);
       // scatterPlot.add(legend);

        this.scatterPlot.addActionListener(this);
        this.add(scatterPlot, BorderLayout.CENTER);


        setupToolbar();
    }
      public void showLegend(boolean show){
        JComponent legend=(JComponent) classifyAgent.getLegendInterative();
        if(show){
            this.scatterPlot.add(legend);

        }
        else{
            this.scatterPlot.remove(legend);
        }
        this.setShowLegend(show);

        scatterPlot.revalidate();
        scatterPlot.repaint();

    }


    public boolean isShowLegend() {
        return showLegend;
    }

    private  void setShowLegend(boolean showLegend) {
        this.showLegend = showLegend;
    }

    protected void setupToolbar(){
        toolbar=new JToolBar();
        this.add(toolbar,BorderLayout.NORTH);
         Dimension buttDim = new Dimension(32, 32);
         JButton button=null;
        //show legend button
          try {
            Class cl = this.getClass();
              ImageIcon imageIcon = ImageUtils.getImageIcon(this.getClass(), "ShowVCLegend.gif");
             /// URL urlGif = cl.getResource("resources/ShowVCLegend.gif");  //fisheye16
             //button = new JButton(new ImageIcon(urlGif));
              button = new JButton(imageIcon);

            button.setPreferredSize(buttDim);


        }
        catch (Exception ex) {
            ex.printStackTrace();
        }

   /*     button.setToolTipText("Show Legend");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                boolean show = SimpleScatterPlot.this.isShowLegend();
                SimpleScatterPlot.this.showLegend(!show);
            }
        });
        toolbar.add(button);*/



        LegendControl legend = this.classifyAgent.getLegendInterative();
        Color xcolor = legend.getXbarColor();
        Color ycolor = legend.getYbarColor();

        JPanel xp=new JPanel();
        JPanel yp=new JPanel();
        JLabel xlb=new JLabel("X:");
        JLabel ylb=new JLabel("Y:");
        JComboBox xcb = getXcb();


        xcb.setForeground(xcolor);  //214,127,21   new Color(0,102,102)
        JComboBox ycb = getYcb();
        ycb.setForeground(ycolor);         //new Color(11, 61, 163)


        xp.add(xlb);
        xp.add(xcb);

        yp.add(ylb);
        yp.add(ycb);
        toolbar.setLayout(new FlowLayout(FlowLayout.LEADING));
        toolbar.add(xp);
        toolbar.add(yp);



    }
    /*******************************************************************************************************
     *                service
     *******************************************************************************************************/
    /**
     * set the 2 variables that displayed in scatter plot.
     * It also update selected variable in the 2 comboBox which list all the variables
     * @param xvarname
     * @param yvarname
     */
    public void setVariables(String xvarname,String yvarname){
        try {
            int[] indexs = classifyAgent.getIndexs(xvarname,yvarname);
            if(indexs.length <2)return;//initially
            if(indexs[0]<0||indexs[indexs.length -1]<0)return;
            this.scatterPlot.setElementPosition(indexs);
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        //this.variableLists.setVariables(xvarname,yvarname);
        //scatterPlot.
    }

    /*******************************************************************************************************
     *                supports
     *******************************************************************************************************/
    /*public void setData(AppDataModel data) {
    this.data = data;
    }*/
    /**
     * If use this methond, the comboBox won't display item description as tooltip since the info is not availalble in the given data[]
     * @param data
     */
    public void setDataSet(Object[] data) {



        //set default data to get color from
        DataSetForApps dataSet = new DataSetForApps();
        dataSet.setDataObject(data);
        this.rowcount = dataSet.getNumObservations();
        //this.scatterPlot.setBackground(Color.white);
        this.scatterPlot.setDataObject(dataSet.getDataSetNumericAndSpatial());
        this.selections = new int[dataSet.getNumObservations()];

        this.classifyAgent.setDataSet(dataSet);

        getYcb().setSelectedIndex(1); //Since scatter plot set it as 1 initially
        toolbar.revalidate();//The comboBox in the toolbar is updated, so need repaint toolbar
        toolbar.repaint();

        this.scatterPlot.setBackground(Color.white);
        displayIndices[0] = 1;
        displayIndices[1] = 2;
        /*this.setXVariable(displayIndices[0]);
        this.setYVariable(displayIndices[1]);*/
        this.scatterPlot.setAxisOn(true);
        this.scatterPlot.setElementPosition(displayIndices);

    }
    public void setDataModel(AppDataModel datamodel) throws Exception {
        this.rowcount=datamodel.getRowCount();
        Object[] rawdata = datamodel.getGeoVistaRawData();
        DataSetForApps dataSet = new DataSetForApps();
        String aClassLoaded = Debug.findWhereAClassLoaded(DataSetForApps.class);
        dataSet.setDataObject(rawdata);
        //this.scatterPlot.setBackground(Color.white);
        Object[] andSpatial = dataSet.getDataSetNumericAndSpatial();
        this.scatterPlot.setDataObject(andSpatial);

        this.selections = new int[dataSet.getNumObservations()];

        this.classifyAgent.setDataModel(datamodel);

        getYcb().setSelectedIndex(1); //Since scatter plot set it as 1 initially
        toolbar.revalidate();//The comboBox in the toolbar is updated, so need repaint toolbar
        toolbar.repaint();

        this.scatterPlot.setBackground(Color.WHITE );
        displayIndices[0] = 1;
        displayIndices[1] = 2;
        /*this.setXVariable(displayIndices[0]);
        this.setYVariable(displayIndices[1]);*/
        this.scatterPlot.setAxisOn(true);
        this.scatterPlot.setElementPosition(displayIndices);
    }
    /*private  static void setNameLists(JComboBox cmb, String [] names){
        DefaultComboBoxModel model = (DefaultComboBoxModel) cmb.getModel();
        model.removeAllElements();
        // model.
        for (int i=0;i<names.length ;i++){
            model.addElement(names[i]);
        }
    }*/
   /* private void setNameIndex(String names[]){
        for (int i=0;i<names.length ;i++){
            this.nameIndexs.put(names[i],new Integer(i+1));
        }
    }*/
    public void setBivarColorClasser(BivariateColorSymbolClassification bivarColorClasser) {
        this.scatterPlot.setBivarColorClasser(bivarColorClasser, false);
    }

    public void setBackgroundColor(Color backgroundColor){
        this.backgroundColor = backgroundColor;
    }

    public Color getBackgroundColor (){
        return this.backgroundColor;
    }

    public void setRegressionClass (Object obj) {
        setRegressionClassName((obj != null) ? obj.getClass().getName() : null);
    }

    public void setRegressionClassName (String classname) {
        this.scatterPlot.setRegressionClassName(classname);
    }
    public Color[] getColors() {
        return this.colorArrays;
    }
    public void setVisualClassifier(ESVisualClassifier vc) {
        //this.legend.setVisualClassifier(vc);
        this.classifyAgent.setVisualClassifier(vc);
        /*if(vc!=null){
                vc.addColorArrayListener(this);
                vc.addClassificationResultListener(this);
            }*/
    }
    /*******************************************************************************************************
     *                events
     *******************************************************************************************************/
    /**
     * Listen to classification result and display 2 variables same as the classification variable.
     * If there is only 1 classification variable, it will update X variable of the scatter plot
     * @param e
     */
    public void classificationResultChanged(ClassificationResultEvent e) {
        /*//1. update legend
        legend.classificationResultChanged(e);
       // external=true;
        //2. update scatter plot
        ClassificationResult cr = e.getClassificationResult();
        if(cr==null)return;
        int colcount = cr.getNumberOfClassX(); //num of category in x direction , it is actually # of column in a legend
        int rowcount = cr.getNumberOfClassY();//num of category in y direction , it is actually # of row in a legend


        if(colcount>0&&rowcount>0){//2 dimension
            String[] attNames = cr.getAttNames();
            if(attNames==null||attNames.length  <2) assert false;
            String xn = attNames[0];
            String yn = attNames[1];
            setVariables(xn,yn);
             this.classifyAgent.setVariables(xn,yn);

        }//if
        else if(colcount>0){//1 dimension
           String[] attNames = cr.getAttNames();
            if(attNames==null||attNames.length  <1) assert false;
            String xn = attNames[0];
            String yn = (String) this.getYcb().getSelectedItem() ;
            setVariables(xn,yn);
            this.classifyAgent.setVariables(xn,yn);
        }//else*/
        this.classifyAgent.classificationResultChanged(e);
    }
    public void dataSetChanged(DataSetEvent e) {
        if( e instanceof AppDataEvent){
            AppDataEvent evt=(AppDataEvent) e;
            AppDataModel dataModel = evt.getDataModel();
            try {
                this.setDataModel((edu.psu.geovista.data.model.AppDataModel) dataModel);
            } catch (Exception e1) {
                //System.out.
                this.setDataSet(e.getDataSet() );
            }
        }
        else
            this.setDataSet(e.getDataSet());
    }

    public void actionPerformed(ActionEvent e) {
        Object src = e.getSource();
        String command = e.getActionCommand();
        if((src == this.scatterPlot) && command.compareTo(ScatterPlot.COMMAND_POINT_SELECTED) == 0){
            //this.selectedObvs = this.scatterPlot.getSelectedObservations();
            //System.out.println("in actionformed");
            this.selections = this.scatterPlot.getSelections();
            this.fireSelectionChanged(this.getSelectedObvs());
        }
    }

    public void colorClassifierChanged(ColorClassifierEvent e) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public void selectionChanged(SelectionEvent e){
        //System.out.println("sending to selectionchanged");
        if (e.getMultipleSlectionColors() != null){
            this.setMultipleSelectionColors(e.getMultipleSlectionColors());
        } else {
            this.setSelectedObvs(e.getSelection());
            //this.scatterPlot.setSelections(e.getSelection());
            this.scatterPlot.repaint();
        }
    }
     public void groupClassifyChanged(GroupClassifyEvent e) {
        GroupClassificationResult cr = e.getClassifyResult();
        Color[] dfcolors=null;
       // Color[] datacolors = ClassificationUtils.getDataColor(cr,dfcolors);


        if(this.rowcount >0){
            dfcolors=new Color[rowcount];
            Arrays.fill(dfcolors, Color.gray);
            /*for (int i=0;i<dfcolors.length ;i++){
                dfcolors[i]=Color.gray ;// the background of map
            }*/
        }
         Color[] datacolors = ClassificationUtils.getDataColor(cr,dfcolors);

         this.scatterPlot.setColorArrayForObs(datacolors);
       

    }

    public void indicationChanged(IndicationEvent e) {
        this.scatterPlot.indicationChanged(e);
    }

    public void colorArrayChanged(ColorArrayEvent e) {
        Color[] colors=e.getColors() ;
        this.scatterPlot.setColorArrayForObs(colors);
    }

    public void variableSelectionChanged(VariableSelectionEvent e) {
        //To change body of implemented methods use File | Settings | File Templates.
    }
    /**
     * adds an IndicationListener to the button
     */
    public void addIndicationListener (IndicationListener l) {
        this.scatterPlot.addIndicationListener(l);
    }

    /**
     * removes an IndicationListener from the button
     */
    public void removeIndicationListener (IndicationListener l) {
        this.scatterPlot.addIndicationListener(l);
    }

    /**
     * implements ColorArrayListener
     */
    public void addColorArrayListener(ColorArrayListener l) {
        listenerList.add(ColorArrayListener.class, l);
        this.fireColorArrayChanged(); //so that if any class registers
    }

    /**
     * removes an ColorArrayListener from the component
     */
    public void removeColorArrayListener(ColorArrayListener l) {
        listenerList.remove(ColorArrayListener.class, l);
    }

    /**
     * Notify all listeners that have registered interest for
     * notification on this event type. The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see javax.swing.event.EventListenerList
     */
    private void fireColorArrayChanged() {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        ColorArrayEvent e = null;

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ColorArrayListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new ColorArrayEvent(this, this.getColors());
                }
                ((ColorArrayListener) listeners[i + 1]).colorArrayChanged(e);
            }
        } //next i
    }
    /**
     * Notify all listeners that have registered interest for
     * notification on this event type. The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see javax.swing.event.EventListenerList
     */
    protected void fireSelectionChanged (int[] newSelection) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        SelectionEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == SelectionListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new SelectionEvent(this, newSelection);
                }
                ((SelectionListener)listeners[i + 1]).selectionChanged(e);
            }
        }//next i

    }
    public void setSelectedObvs (int[] selected) {

        if(selected == null){
            return;
        }else{
            for(int i = 0; i < this.selections.length; i++){
                this.selections[i] = 0;
            }
            for(int i = 0; i < selected.length; i++){
                this.selections[selected[i]] = 1;
            }
        }
        this.multipleSelectionColors = null;
        this.scatterPlot.setSelections(this.selections);
        repaint();
    }

    /**
     * Return index array for selected observations.
     * @return
     */
    public int[] getSelectedObvs() {
        //need to transform data length long int[] to short int[]
        Vector selectedObvs = new Vector();
        for(int i = 0; i < this.selections.length; i ++){
            if (this.selections[i] == 1){
                selectedObvs.add(new Integer(i));
            }
        }
        int[] selectedShortArray = new int[selectedObvs.size()];
        for (int i = 0; i < selectedObvs.size(); i++) {
            selectedShortArray[i] = ( (Integer) selectedObvs.get(i)).intValue();
        }
        return selectedShortArray;
    }

    public void setMultipleSelectionColors(Color[] multipleSelectionColors){
        this.multipleSelectionColors = multipleSelectionColors;
        this.scatterPlot.setMultipleSelectionColors(this.multipleSelectionColors);
    }

    public ScatterPlot getScatterPlot() {
        return scatterPlot;
    }

    /**
     * adds an SelectionListener.
     * @see javax.swing.event.EventListenerList
     */
    public void addSelectionListener (SelectionListener l) {
        listenerList.add(SelectionListener.class, l);

        LegendControl2 legend = (LegendControl2) this.classifyAgent.getLegendInterative();
        DefaultEventSupport eventSupport = legend.getEventSupport();
        eventSupport.addSelectionListener(l);
    }
    /**
     * removes an SelectionListener from the component.
     * @see javax.swing.event.EventListenerList
     */
    public void removeSelectionListener (SelectionListener l) {
        listenerList.remove(SelectionListener.class, l);

    }

    public void addClassifySettingListener(ClassifySettingListener l){
          //todo removeClassifySettingListener(l);
          listenerList.add( ClassifySettingListener.class, l);
    }
    public void removeClassifySettingListener(ClassifySettingListener l){
          listenerList.remove( ClassifySettingListener.class, l);
    }
    private void fireClassifySettingChanged(ClassifySetting setting){
          Object[] listeners = listenerList.getListenerList();
          ClassifySettingEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ClassifySettingListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new ClassifySettingEvent( this,setting);
                }
                ((ClassifySettingListener)listeners[i + 1]).classifySettingChanged(e);
            }
        }//next i
    }

    private JComboBox getXcb() {
        return this.classifyAgent.getXcb();
    }



    private JComboBox getYcb() {
        return this.classifyAgent.getYcb();
    }


    private class MyScatterPlot extends ScatterPlot{
        SimpleScatterPlot owner;
        JPopupMenu popup;

        public MyScatterPlot(SimpleScatterPlot owner) {
            this.owner = owner;
        }

        protected void maybeShowPopup (MouseEvent e) {

            //getPopup().show(SimpleScatterPlot.this, e.getX(), e.getY());
       }
      

        public JToolBar getTools() {
            return null;  //To change body of implemented methods use File | Settings | File Templates.
        }
    }


}
